//     Originally from https://github.com/googlecast/cast-custom-receiver
"use strict";

window.castReceiverManager = null;
window.mediaManager = null;
window.messageBus = null;
window.mediaElement = null;
window.mediaHost = null;
window.mediaProtocol = null;
window.mediaPlayer = null;
window.connectedCastSenders = []; // {senderId:'', channel:obj}

function setHudMessage(elementId, message, data) {
    document.getElementById(elementId).innerHTML = 
	'' + message + (data != null ? JSON.stringify(data) : "");
}

window.onload = function() {
    window.mediaElement = document.getElementById('receiverVideoElement');
    window.mediaElement.autoplay = true;

    var HUD = { 
	APP:      "applicationState",
	RX :      "castReceiverManagerMessage",
	ELEMENT:  "mediaElementState",
	HOST:     "mediaHostState", 
	MANAGER:  "mediaManagerMessage",
	PROTOCOL: "mediaProtocol",
	BUS:      "messageBusMessage",
	COUNT:    "sessionCount",
	VOLUME:   "volumeMessage"
    };

    var Log = {
	i : console.log.bind(console), 
	e : console.error.bind(console),
	w : console.warn.bind(console)
    };
    /**
     * - play           – The process of play has started
     * - waiting        – When the video stops due to buffering
     * - volumechange   – volume has changed
     * - stalled        – trying to get data, but not available
     * - ratechange     – some speed changed
     * - canplay        – It is possible to start playback, but no 
     *                    guarantee of not buffering
     * - canplaythrough – It seems likely that we can play w/o 
     *                    buffering issues
     * - ended          – the video has finished
     * - error          – error occured during loading of the video
     * - playing        – when the video has started playing
     * - seeking        – started seeking
     * - seeked         – seeking has completed
     * 
     * http://www.w3.org/2010/05/video/mediaevents.html for more info.
    **/
    window.mediaElement.addEventListener('loadstart', function(e){
        Log.i("######### MEDIA ELEMENT LOAD START");
        setHudMessage(HUD.ELEMENT,'Load Start');
    });
    window.mediaElement.addEventListener('loadeddata', function(e){
        Log.i("######### MEDIA ELEMENT DATA LOADED");
        setHudMessage(HUD.ELEMENT,'Data Loaded');
    });
    window.mediaElement.addEventListener('canplay', function(e){
        Log.i("######### MEDIA ELEMENT CAN PLAY");
        setHudMessage(HUD.ELEMENT,'Can Play');
    });
    window.mediaElement.addEventListener('ended', function(e){
        Log.i("######### MEDIA ELEMENT ENDED");
        setHudMessage(HUD.ELEMENT,'Ended');
    });
    window.mediaElement.addEventListener('playing', function(e){
        Log.i("######### MEDIA ELEMENT PLAYING");
        setHudMessage(HUD.ELEMENT,'Playing');
    });
    window.mediaElement.addEventListener('waiting', function(e){
        Log.i("######### MEDIA ELEMENT WAITING");
        setHudMessage(HUD.ELEMENT,'Waiting');
    });
    window.mediaElement.addEventListener('stalled', function(e){
        Log.i("######### MEDIA ELEMENT STALLED");
        setHudMessage(HUD.ELEMENT,'Stalled');
    });
    window.mediaElement.addEventListener('error', function(e){
        Log.i("######### MEDIA ELEMENT ERROR " + e);
        setHudMessage(HUD.ELEMENT,'Error');
    });
    window.mediaElement.addEventListener('abort', function(e){
        Log.i("######### MEDIA ELEMENT ABORT " + e);
        setHudMessage(HUD.ELEMENT,'Abort');
    });
    window.mediaElement.addEventListener('suspend', function(e){
        Log.i("######### MEDIA ELEMENT SUSPEND " + e);
        setHudMessage(HUD.ELEMENT,'Suspended');
    });
    window.mediaElement.addEventListener('progress', function(e){
        setHudMessage(HUD.ELEMENT,'Progress');
    });

    window.mediaElement.addEventListener('seeking', function(e){
        Log.i("######### MEDIA ELEMENT SEEKING " + e);
        setHudMessage(HUD.ELEMENT,'Seeking');
    });
    window.mediaElement.addEventListener('seeked', function(e){
        Log.i("######### MEDIA ELEMENT SEEKED " + e);
        setHudMessage(HUD.ELEMENT,'Seeked');
    });

    /**
     * Sets the log verbosity level.
     *
     * - DEBUG   - Debug logging (all messages). 
     * - VERBOSE - Verbose logging (sender messages).
     * - INFO    - Info logging (events, general logs).
     * - ERROR   - Error logging (errors).
     * - NONE    - No logging.
     * 
     **/
    cast.receiver.logger.setLevelValue(cast.receiver.LoggerLevel.DEBUG);
    
    window.castReceiverManager = 
	cast.receiver.CastReceiverManager.getInstance();

    /**
     * Called to process 'ready' event. Only called after calling
     * castReceiverManager.start(config) and the system becomes ready
     * to start receiving messages.
     *
     * @param {cast.receiver.CastReceiverManager.Event} event - can be null
     *
     * There is no default handler
     */
    window.castReceiverManager.onReady = function(event) {
        Log.i("### Cast Receiver Manager is READY: " + JSON.stringify(event));
        setHudMessage(HUD.RX,'READY: ',event);
        setHudMessage(HUD.APP,'Loaded. Started. Ready.');
    }

    /**
     * If provided, it processes the 'senderconnected' event.  Called
     * to process the 'senderconnected' event.
     *
     * @param {cast.receiver.CastReceiverManager.Event} event - can be null
     *
     * There is no default handler
     */
    window.castReceiverManager.onSenderConnected = function(event) {
        Log.i("### Cast Receiver Manager - Sender Connected : " + 
	      JSON.stringify(event));
        setHudMessage(HUD.RX,'Sender Connected: ',event);

        // TODO - add sender and grab CastChannel from
        // CastMessageBus.getCastChannel(senderId)
        var senders = window.castReceiverManager.getSenders();
        setHudMessage(HUD.COUNT, '' + senders.length);
    }

    /**
     * If provided, it processes the 'senderdisconnected' event.
     * Called to process the 'senderdisconnected' event.
     *
     * @param {cast.receiver.CastReceiverManager.Event} event - can be null
     *
     * There is no default handler
     */
    window.castReceiverManager.onSenderDisconnected = function(event) {
        Log.i("### Cast Receiver Manager - Sender Disconnected : " + 
	      JSON.stringify(event));
        setHudMessage(HUD.RX,'Sender Disconnected: ',event);

        var senders = window.castReceiverManager.getSenders();
        setHudMessage(HUD.COUNT, '' + senders.length);
    }

    /**
     * If provided, it processes the 'systemvolumechanged' event.
     * Called to process the 'systemvolumechanged' event.
     *
     * @param {cast.receiver.CastReceiverManager.Event} event - can be null
     *
     * There is no default handler
     */
    window.castReceiverManager.onSystemVolumeChanged = function(event) {
        Log.i("### Cast Receiver Manager - System Volume Changed : " + 
	      JSON.stringify(event));
        setHudMessage(HUD.RX,'System Volume Changed: ',event);

        // See cast.receiver.media.Volume
        Log.i("### Volume: " + event.data['level'] + " is muted? " + 
	      event.data['muted']);
        setHudMessage(HUD.VOLUME, 'Level: ' + event.data['level'] + 
		      ' -- muted? ' + event.data['muted']);
    }

    /**
     * Called to process the 'visibilitychanged' event.
     *
     * Fired when the visibility of the application has changed (for
     * example after a HDMI Input change or when the TV is turned
     * off/on and the cast device is externally powered). Note that
     * this API has the same effect as the webkitvisibilitychange
     * event raised by your document, we provided it as
     * CastReceiverManager API for convenience and to avoid a
     * dependency on a webkit-prefixed event.
     *
     * @param {cast.receiver.CastReceiverManager.Event} event - can be null
     *
     * There is no default handler for this event type.
     */
    window.castReceiverManager.onVisibilityChanged = function(event) {
        Log.i("### Cast Receiver Manager - Visibility Changed : " + 
	      JSON.stringify(event));
        setHudMessage(HUD.RX,'Visibility Changed: ',event);

        /** check if visible and pause media if not - add a timer to
            tear down after a period of time if visibilty does not
            change back **/
        if (event.data) { // It is visible
            window.mediaElement.play(); // Resume media playback
            window.clearTimeout(window.timeout); // Turn off the timeout
            window.timeout = null;
        } else {
            window.mediaElement.pause(); // Pause playback
            window.timeout = window.setTimeout(function(){window.close();}, 
					       10000); // 10 seconds timeout
        }
    }


    /**
     * ALTERNATIVE TO onVisibilityChanged
     *
     * Use this to know when the user switched away from the Cast
     * device input. It depends on the TV Supporting CEC
     **/
    document.addEventListener('webkitvisibilitychange', function(){
        if(document.webkithidden) {
            window.mediaElement.pause(); // Pause playback
            window.timeout = window.setTimeout(function(){window.close();}, 
					       10000); // 10 Minute timeout
        } else {
            window.mediaElement.play(); // Resume media playback
            window.clearTimeout(window.timeout); // Turn off the timeout
            window.timeout = null;
        }
    });


    /**
     * Use the messageBus to listen for incoming messages on a virtual
     * channel using a namespace string.  Also use messageBus to send
     * messages back to a sender or broadcast a message to all
     * senders.  You can check the
     * cast.receiver.CastMessageBus.MessageType that a message bus
     * processes though a call to getMessageType. As well, you get the
     * namespace of a message bus by calling getNamespace()
     */
    window.messageBus = 
	window.castReceiverManager.getCastMessageBus('urn:x-cast:com.google.devrel.custom');
    /**
     * The namespace urn:x-cast:com.google.devrel.custom is used to
     * identify the protocol of showing/hiding the heads up display
     * messages (The messages defined at the beginning of the html).
     *
     * The protocol consists of one string message: show In the case
     * of the message value not being show - the assumed value is
     * hide.
     **/
    window.messageBus.onMessage = function(event) {
        Log.i("### Message Bus - Media Message: " + JSON.stringify(event));
        setHudMessage(HUD.MESSAGEBUS, event);

        Log.i("### CUSTOM MESSAGE: " + JSON.stringify(event));
        // show/hide messages
        Log.i(event['data']);
        if(event['data']==='show') {
            document.getElementById('messages').style.display = 'block';
        } else {
            document.getElementById('messages').style.display = 'none';
        }
    }


    // This class is used to send/receive media messages/events using
    // the media protocol/namesapce
    // (urn:x-cast:com.google.cast.media).
    window.mediaManager = new cast.receiver.MediaManager(window.mediaElement);

    /**
     * Called when the media ends.
     *
     * mediaManager.resetMediaElement(cast.receiver.media.IdleReason.FINISHED);
     **/
    window.mediaManager['onEndedOrig'] = window.mediaManager.onEnded;
    /**
     * Called when the media ends
     */
    window.mediaManager.onEnded = function() {
        Log.i("### Media Manager - ENDED: " + JSON.stringify(event));
        setHudMessage(HUD.MANAGER, 'ENDED');

        window.mediaManager['onEndedOrig']();
    }

    /**
     * Default implementation of onError.
     *
     * mediaManager.resetMediaElement(cast.receiver.media.IdleReason.ERROR)
     **/
    window.mediaManager['onErrorOrig'] = window.mediaManager.onError;
    /**
     * Called when there is an error not triggered by a LOAD request
     *
     * @param obj
     */
    window.mediaManager.onError = function(obj) {
        Log.i("### Media Manager - error: " + JSON.stringify(obj));
        setHudMessage(HUD.MANAGER,'ERROR - ',obj);

        window.mediaManager['onErrorOrig'](obj);
    }

    /**
     * Processes the get status event.
     *
     * Sends a media status message to the requesting sender
     * (event.data.requestId)
     **/
    window.mediaManager['onGetStatusOrig'] = window.mediaManager.onGetStatus;
    /**
     * Processes the get status event.
     *
     * @param event
     */
    window.mediaManager.onGetStatus = function(event) {
        Log.i("### Media Manager - GET STATUS: " + JSON.stringify(event));
        setHudMessage(HUD.MANAGER,'GET STATUS ',event);

        window.mediaManager['onGetStatusOrig'](event);
    }

    /**
     * Default implementation of onLoadMetadataError.
     *
     * mediaManager.resetMediaElement(cast.receiver.media.IdleReason.ERROR, false);
     * mediaManager.sendLoadError(cast.receiver.media.ErrorType.LOAD_FAILED);
     **/
    window.mediaManager['onLoadMetadataErrorOrig'] = 
	window.mediaManager.onLoadMetadataError;
    /**
     * Called when load has had an error, overridden to handle
     * application specific logic.
     *
     * @param event
     */
    window.mediaManager.onLoadMetadataError = function(event) {
        Log.i("### Media Manager - LOAD METADATA ERROR: " + 
	      JSON.stringify(event));
        setHudMessage(HUD.MANAGER,'LOAD METADATA ERROR: ',event);

        window.mediaManager['onLoadMetadataErrorOrig'](event);
    }

    /**
     * Default implementation of onMetadataLoaded
     *
     * Passed a cast.receiver.MediaManager.LoadInfo event object.
     *
     * Sets the mediaElement.currentTime = loadInfo.message.currentTime.
     *
     * Sends the new status after a LOAD message has been completed
     * succesfully.
     *
     * Note: Applications do not normally need to call this API.  When
     * the application overrides onLoad, it may need to manually
     * declare that the LOAD request was sucessful. The default
     * implementaion will send the new status to the sender when the
     * video/audio element raises the 'loadedmetadata' event.  The
     * default behavior may not be acceptable in a couple scenarios:
     *
     * 1) When the application does not want to declare LOAD succesful until for
     *    example 'canPlay' is raised (instead of 'loadedmetadata').
     * 2) When the application is not actually loading the media element (for
     *    example if LOAD is used to load an image).
     **/
    window.mediaManager['onLoadMetadataOrig'] = 
	window.mediaManager.onLoadMetadataLoaded;
    /**
     * Called when load has completed, overridden to handle
     * application specific logic.
     *
     * @param event
     */
    window.mediaManager.onLoadMetadataLoaded = function(event) {
        Log.i("### Media Manager - LOADED METADATA: " + JSON.stringify(event));
        setHudMessage(HUD.MANAGER,'LOADED METADATA: ',event);

        window.mediaManager['onLoadMetadataOrig'](event);
    }

    /**
     * Processes the pause event.
     *
     * mediaElement.pause();
     *
     * Broadcast (without sending media information) to all senders
     * that pause has happened.
     **/
    window.mediaManager['onPauseOrig'] = window.mediaManager.onPause;
    /**
     * Process pause event
     *
     * @param event
     */
    window.mediaManager.onPause = function(event) {
        Log.i("### Media Manager - PAUSE: " + JSON.stringify(event));
        setHudMessage(HUD.MANAGER,'PAUSE: ',event);

        window.mediaManager['onPauseOrig'](event);
    }

    /**
     * Default - Processes the play event.
     *
     * mediaElement.play();
     *
     **/
    window.mediaManager['onPlayOrig'] = window.mediaManager.onPlay;
    /**
     * Process play event
     *
     * @param event
     */
    window.mediaManager.onPlay = function(event) {
        Log.i("### Media Manager - PLAY: " + JSON.stringify(event));
        setHudMessage(HUD.MANAGER,'PLAY: ',event);

        window.mediaManager['onPlayOrig'](event);
    }

    /**
     * Default implementation of the seek event.
     *
     * Sets the mediaElement.currentTime to event.data.currentTime.
     *
     * - If the event.data.resumeState is
     *   cast.receiver.media.SeekResumeState.PLAYBACK_START and the 
     *   mediaElement is paused then call mediaElement.play(). 
     * 
     * - Otherwise if event.data.resumeState is
     *   cast.receiver.media.SeekResumeState.PLAYBACK_PAUSE and the
     *   mediaElement is not paused, call mediaElement.pause().
     *
     * - Broadcast (without sending media information) to all senders
     *   that seek has happened.
     **/
    window.mediaManager['onSeekOrig'] = window.mediaManager.onSeek;
    /**
     * Process seek event
     *
     * @param event
     */
    window.mediaManager.onSeek = function(event) {
        Log.i("### Media Manager - SEEK: " + JSON.stringify(event));
        setHudMessage(HUD.MANAGER,'SEEK: ',event);

        window.mediaManager['onSeekOrig'](event);
    }

    /**
     * Default implementation of the set volume event.
     *
     * - Checks event.data.volume.level is defined and sets the
     *   mediaElement.volume to the value
     * - Checks event.data.volume.muted is defined and sets the
     *   mediaElement.muted to the value
     * - Broadcasts (without sending media information) to all senders
     *   that the volume has changed.
     **/
    window.mediaManager['onSetVolumeOrig'] = window.mediaManager.onSetVolume;
    /**
     * Process set volume event
     *
     * @param event
     */
    window.mediaManager.onSetVolume = function(event) {
        Log.i("### Media Manager - SET VOLUME: " + JSON.stringify(event));
        setHudMessage(HUD.MANAGER,'SET VOLUME: ',event);

        window.mediaManager['onSetVolumeOrig'](event);
    }

    /**
     * Processes the stop event.
     *
     * window.mediaManager.resetMediaElement(cast.receiver.media.IdleReason.CANCELLED, true, event.data.requestId);
     *
     * Resets Media Element to IDLE state. After this call the
     * mediaElement properties will change, paused will be true,
     * currentTime will be zero and the src attribute will be
     * empty. This only needs to be manually called if the developer
     * wants to override the default behavior of onError, onStop or
     * onEnded, for example.
     **/
    window.mediaManager['onStopOrig'] = window.mediaManager.onStop;
    /**
     * Process stop event
     *
     * @param event
     */
    window.mediaManager.onStop = function(event) {
        Log.i("### Media Manager - STOP: " + JSON.stringify(event));
        setHudMessage(HUD.MANAGER,'STOP: ',event);

        window.mediaManager['onStopOrig'](event);
    }

    /**
     * Default implementation for the load event.
     *
     * Sets the mediaElement.autoplay to false.  Checks that
     * data.media and data.media.contentId are valid then sets the
     * mediaElement.src to the data.media.contentId.
     *
     * Checks the data.autoplay value:
     *   - if undefined sets mediaElement.autoplay = true
     *   - if has value then sets mediaElement.autoplay to that value
     **/
    window.mediaManager['onLoadOrig'] = window.mediaManager.onLoad;
    /**
     * Processes the load event.
     *
     * @param event
     */
    window.mediaManager.onLoad = function(event) {
        Log.i("### Media Manager - LOAD: " + JSON.stringify(event));
	Log.i("What is WrapXHR: " + window.WrapXHR);
        setHudMessage(HUD.MANAGER,'LOAD ',event);

        // TODO - setup for load here
        // TODO - if there is an error during load: call mediaManager.sendLoadError to notify sender
        // TODO - if there is no error call mediaManager.sendLoadCompleteComplete
        // TODO - call mediaManager.setMediaInformation(MediaInformation)

        if(window.mediaPlayer !== null) {
            window.mediaPlayer.unload(); // Ensure unload before loading again
        }

        if (event.data['media'] && event.data['media']['contentId']) {
            var url = event.data['media']['contentId'];
	    var typ = event.data['media']['contentType'];

            window.mediaHost = new cast.player.api.Host({
                'mediaElement': window.mediaElement,
                'url': url
            });

            window.mediaHost.onError = function (errorCode) {
                Log.e('### HOST ERROR - Fatal Error: code = ' + errorCode);
                setHudMessage(HUD.HOST, 'Fatal Error: code = ' + errorCode);
                if (window.mediaPlayer !== null) {
                    window.mediaPlayer.unload();
                }
            }

            var initialTimeIndexSeconds = event.data['media']['currentTime']||0;
            // TODO: real code would know what content it was going to
            // access and this would not be here.
            var protocol = null;
	    var host     = window.mediaHost;
            var parser   = document.createElement('a');
            parser.href  = url;

            var ext = ext = parser.pathname.split('.').pop();
            if (typ == "application/x-mpegurl" || ext === 'm3u8') {
                protocol = cast.player.api.CreateHlsStreamingProtocol(host);
            } else if (ext === 'mpd') {
                protocol = cast.player.api.CreateDashStreamingProtocol(host);
            } else if (ext === 'ism/') {
                protocol = cast.player.api.CreateSmoothStreamingProtocol(host);
            }
            Log.i('### Media Protocol Identified as ' + ext);
            setHudMessage(HUD.PROTOCOL, ext);

            if (protocol === null) {
                // Call on original handler
                window.mediaManager['onLoadOrig'](event);
            } else {
                // Advanced Playback - HLS, MPEG DASH, SMOOTH
                // STREAMING.  Player registers to listen to the media
                // element events through the mediaHost property of
                // the mediaElement
                window.mediaPlayer = 
		    new cast.player.api.Player(window.mediaHost);
                window.mediaPlayer.load(protocol, initialTimeIndexSeconds);
            }
        }

    }

    Log.i('### Application Loaded. Starting system.');
    setHudMessage(HUD.APP,'Loaded. Starting up.');

    /**
     * Application config
     **/
    var appConfig = new cast.receiver.CastReceiverManager.Config();

    /**
     * Text that represents the application status. It should meet
     * internationalization rules as may be displayed by the sender
     * application.
     *
     * @type {string|undefined}
     **/
    appConfig.statusText = 'Ready to play';

    /**
     * Maximum time in seconds before closing an idle
     * sender connection. Setting this value enables a heartbeat message to keep
     * the connection alive. Used to detect unresponsive senders faster than
     * typical TCP timeouts. The minimum value is 5 seconds, there is no upper
     * bound enforced but practically it's minutes before platform TCP timeouts
     * come into play. Default value is 10 seconds.
     *
     * @type {number|undefined}
     **/
    appConfig.maxInactivity = 6000;

    /**
     * Initializes the system manager. The application should call
     * this method when it is ready to start receiving messages,
     * typically after registering to listen for the events it is
     * interested on.
     */
    window.castReceiverManager.start(appConfig);
}
